<?php

class Kd_Data_Dcl_Parser_Mysql extends Kd_Data_Dcl_Parser_Base {

    private static $_compareFlags = array('gt' => '>', 'lt' => '<', 'eq' => '=', 'gteq' => '>=', 'lteq' => '<=', 'neq' => '!=');

    public function __construct() {
        parent::__construct();
    }

    protected function parseQuery(Kd_Data_Dcl_Query $query) {
        $result = $this->parseSelect($query->getSelect());
        $result .= $this->parseFrom($query->getFrom());
        if ($query->getJoin() && !$query->getJoin()->getChildExpressions()->isEmpty()) {
            $result .= $this->parseJoin($query->getJoin());
        }
        if ($query->getWhere() && !$query->getWhere()->getChildExpressions()->isEmpty()) {
            $result .= ' WHERE '.$this->parseWhere($query->getWhere());
        }
        if ($query->getOrderBy() && !$query->getOrderBy()->getChildExpressions()->isEmpty()) {
            $result .= $this->parseOrderBy($query->getOrderBy());
        }
        if($query->getGroupBy() && !$query->getGroupBy()->getChildExpressions()->isEmpty()){
        	$result .= $this->parseGroupBy($query->getGroupBy());
        }
        return $result;
    }

    protected function parseInsert(Kd_Data_Dcl_Insert $insert) {
        $fieldNames = array_keys($insert->getValues());
        $fieldValues = array_values($insert->getValues());
        foreach ($fieldNames as &$name) {
            $name = '`' . $name . '`';
        }
        foreach ($fieldValues as &$fv) {
            if (is_null($fv)) {
                $fv = 'NULL';
            }
        }
        $result = 'INSERT INTO `' . $insert->getTable() . '`';
        $result .= ' (' . implode(',', $fieldNames) . ')';
        $result .= ' VALUES(' . implode(',', $fieldValues) . ')';
        return $result;
    }

    protected function parseUpdate(Kd_Data_Dcl_Update $update) {
        $fieldSet = array();
        foreach ($update->getValues() as $name => $value) {
            $fieldSet[] = '`' . $name . '` = ' . (is_null($value) ? 'NULL' : $this->parseExpression($value));
        }
        $result = ' UPDATE `' . $update->getTable() . '`';
        $result .= ' SET ' . implode(',', $fieldSet);
        $result .= ' WHERE '.$this->parseWhere($update->getWhere());
        return $result;
    }

    protected function parseDelete(Kd_Data_Dcl_Delete $delete) {
        $result = 'DELETE FROM `' . $delete->getTable() . '`';
        $result .= ' WHERE '.$this->parseWhere($delete->getWhere());
        return $result;
    }

    protected function parseSelect(Kd_Data_Dcl_Expression_Select $select) {
        $fields = array();
        if ($select->getIsAll()) {
            $fields[] = '*';
        } else {
            foreach ($select->getChildExpressions() as $expression) {
                $fields[] = $this->parseField($expression);
            }
        }
        return 'SELECT ' . ($select->getIsDistinct() ? 'DISTINCT ' : '') . implode(',', $fields);
    }

    protected function parseFrom(Kd_Data_Dcl_Expression_From $from) {
        return ' FROM ' . $this->parseTable($from);
    }

    protected function parseJoin(Kd_Data_Dcl_Expression_Join $join) {
        $result = '';
        foreach ($join->getChildExpressions() as $expression) {
            $result .= ' ' . strtoupper($expression->getRelationType()) . ' JOIN ';
            $result .= ' ' . $this->parseTable($expression->getTable());
            $result .= ' ON ' . $this->parseField($expression->getLeft()) . ' = ' . $this->parseField($expression->getRight());
        }
        return $result;
    }

    protected function parseWhere(Kd_Data_Dcl_Expression_Where_Base $where) {
        $conditions = array();
        foreach ($where->getChildExpressions() as $expression) {
            if ($expression instanceof Kd_Data_Dcl_Expression_Not) {
                $conditions[] = 'NOT';
            } elseif ($expression instanceof Kd_Data_Dcl_Expression_Where_Group) {
                $conditions[] = '(' . $this->parseWhere($expression) . ')';
                $logicFlag = $expression->getLogicFlag();
                if (!is_null($logicFlag)) {
                    $conditions[] = strtoupper($logicFlag);
                }
            } else {
                $conditions[] = $this->parseCondition($expression);
                $logicFlag = $expression->getLogicFlag();
                if (!is_null($logicFlag)) {
                    $conditions[] = strtoupper($logicFlag);
                }
            }
        }

        return implode(' ', $conditions);
    }

    protected function parseOrderBy(Kd_Data_Dcl_Expression_Orderby $orderby) {
        $sorts = array();
        foreach ($orderby->getChildExpressions() as $expression) {
            $sorts[] = $this->parseExpression($expression);
        }
        return ' ORDER BY ' . implode(',', $sorts);
    }
    
    protected function parseGroupBy(Kd_Data_Dcl_Expression_Groupby $groupby) {
        $fields = array();
        foreach ($groupby->getChildExpressions() as $expression) {
            $fields[] = $this->parseExpression($expression);
        }
        $result = ' GROUP BY ' . implode(',', $fields);
        if(!$groupby->getHaving()){
			$result .= ' HAVING '.$this->parseWhere($groupby->getHaving());
        }
        return $result;
    }    

    protected function parseCondition(Kd_Data_Dcl_Expression_Where_Condition_Base $condition) {
        $result = $condition instanceof Kd_Data_Dcl_Expression_Where_Condition_Nullable ? '' : $this->parseExpression($condition->getLeft());
        switch ($condition->getConditionType()) {
            case 'compare':
                if ($condition instanceof Kd_Data_Dcl_Expression_Where_Condition_Not) {
                    if ($condition->getIsNot()) {
                        $result .= ' NOT';
                    }
                }
                $result .= ' ' . self::$_compareFlags[$condition->getFlag()];
                $result .= ' ' . $this->parseExpression($condition->getRight());
                break;
            case 'like':
                if ($condition instanceof Kd_Data_Dcl_Expression_Where_Condition_Not) {
                    if ($condition->getIsNot()) {
                        $result .= ' NOT';
                    }
                }
                $result .= ' LIKE ' . $this->parseExpression($condition->getRight());
                break;
            case 'in':
                if ($condition instanceof Kd_Data_Dcl_Expression_Where_Condition_Not) {
                    if ($condition->getIsNot()) {
                        $result .= ' NOT';
                    }
                }
                $result .= ' IN(' . $this->parseExpression($condition->getRight()) . ')';
                break;
            case 'isNull':
                $result .= ' ' . $this->parseExpression($condition->getField());
                if ($condition instanceof Kd_Data_Dcl_Expression_Where_Condition_Not) {
                    if ($condition->getIsNot()) {
                        $result .= ' IS NOT NULL';
                    } else {
                        $result .= ' IS NULL';
                    }
                }
                break;
        }
        return $result;
    }

    protected function parseTable(Kd_Data_Dcl_Expression_Table $table) {
        $result = '`' . $table->getName() . '`';
        if ($table->getAlias()) {
            $result .= ' AS `' . $table->getAlias() . '`';
        }
        return $result;
    }

    protected function parseField(Kd_Data_Dcl_Expression_Field $field) {
        $result = $field->getTableName() ? '`' . $field->getTableName() . '`.' : '';
        if ($field->getName() instanceof Kd_Data_Dcl_Expression_Base) {
            $result .= $this->parseExpression($field->getName());
        } else {
            $result .= $field->getName() != '*' ? '`' . $field->getName() . '`' : '*';
        }

        if ($field instanceof Kd_Data_Dcl_Expression_Orderby_Field) {
            $result .= ' ' . strtoupper($field->getDirection());
        } elseif ($field instanceof Kd_Data_Dcl_Expression_Select_Field) {
            if ($field->getAlias()) {
                $result .= ' AS `' . $field->getAlias() . '`';
            }
        }

        return $result;
    }

    protected function parseFunction($function) {
        switch ($function->getFunctionType()) {
            case 'count':
                $field = $function->getField() == '*' ? '*' : $this->parseExpression($function->getField());
                return 'COUNT(' . $field . ')';
                break;
            case 'arithmetic':
                $left = $this->parseExpression($function->getLeft());
                $right = $this->parseExpression($function->getRight());
                switch ($function->getFlag()) {
                    case '+':
                        return '(' . $left . '+' . $right . ')';
                    case '-':
                        return '(' . $left . '-' . $right . ')';
                    case '*':
                        return '(' . $left . '*' . $right . ')';
                    case '/':
                        return '(' . $left . '/' . $right . ')';
                    case '%':
                        return '(' . $left . '%' . $right . ')';
                }
                break;
            case 'max':
                return 'MAX(' . $this->parseExpression($function->getField()) . ')';
            case 'min':
                return 'MIN(' . $this->parseExpression($function->getField()) . ')';
            case 'avg':
                return 'AVG(' . $this->parseExpression($function->getField()) . ')';
            case 'string_sub':
                $result = 'substring(' . $this->parseExpression($function->getString()) . ',' . $function->getPosition();
                if ($function->getLength()) {
                    $result .= ',' . $function->getLength();
                }
                return $result . ')';
            case 'string_concat':
                $result = 'concat(';
                foreach ($function->getStrings() as $str) {
                    $result .= $this->parseExpression($str) . ',';
                }
                return substr($result, 0, -1) . ')';
            case 'string_length':
                return $result = 'length('. $this->parseExpression($function->getField()) .')';
        }
    }

    protected function parseExpression($expression) {
        if ($expression instanceof Kd_Data_Dcl_Expression_Field) {
            return $this->parseField($expression);
        } elseif ($expression instanceof Kd_Data_Dcl_Expression_Fn_Base) {
            return $this->parseFunction($expression);
        } elseif ($expression instanceof Kd_Data_Dcl_Expression_Subquery) {
            $expression->getOwner()->addParameter($expression->getQuery()->getParameters());
            return $this->parseQuery($expression->getQuery());
        } else {
            return $expression;
        }
    }

}